<template>
<div>
  <!-- 面包屑导航区 -->
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>权限管理</el-breadcrumb-item>
      <el-breadcrumb-item>角色列表</el-breadcrumb-item>
    </el-breadcrumb>
    <!-- 卡片试图 -->
    <el-card>
      <!-- 添加角色按钮区域 -->
      <el-row>
        <el-col>
          <el-button type="primary">添加角色</el-button>
        </el-col>
      </el-row>
      <!-- 角色列表区域 -->
      <el-table :data="roleList" border stripe>
        <el-table-column type="expand">
          <template slot-scope="scope">
            <el-row :class="['bdbottom', i1 === 0 ? 'bdtop' : '','vcenter']" v-for="(item1,i1) in scope.row.children" :key="item1.id">
              <!-- 渲染一级权限 -->
              <el-col :span="5" >
                <el-tag closable @close="removeRightById(scope.row,item1.id)">{{item1.authName}}</el-tag>
                <i class="el-icon-caret-right"></i>
              </el-col>
              <!-- 渲染二级和三级权限 -->
              <el-col :span="19">
                <!-- 通过for循环嵌套渲染二级权限 -->
                <el-row :class="[i2 === 0 ? '': 'bdtop','vcenter']" v-for="(item2,i2) in item1.children" :key="item2.id">
                  <el-col :span="8">
                    <el-tag type="success" closable @close="removeRightById(scope.row,item2.id)">{{item2.authName}}</el-tag>
                    <i class="el-icon-caret-right"></i>
                  </el-col>
                  <el-col :span="16">
                    <el-tag v-for="(item3) in item2.children" :key="item3.id" type="warning" closable
                    @close="removeRightById(scope.row,item3.id)">{{item3.authName}}</el-tag>
                  </el-col>
                </el-row>
              </el-col>
            </el-row>
            <!-- <pre>{{scope.row}}</pre> -->
          </template>
        </el-table-column>
        <el-table-column type="index"></el-table-column>
        <el-table-column label="角色名称" prop="roleName"></el-table-column>
        <el-table-column label="角色描述" prop="roleDesc"></el-table-column>
        <el-table-column label="操作" width="300px">
         <template slot-scope="scope">
           <el-button size="mini" type="primary" icon="el-icon-search">搜索</el-button>
           <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
           <el-button size="mini" type="warning" icon="el-icon-setting" @click="showSetRightDialog(scope.row)">分配权限</el-button>
         </template>
        </el-table-column>
      </el-table>

    </el-card>
    <!-- 分配权限对话框 -->
      <el-dialog
    title="分配权限"
    :visible.sync="SetRightDialogVisible"
    width="50%"
    @close="SetRightDialogClosed"
   >
    <!-- <span>这是一段信息</span> -->
    <!-- 树形控件 -->
    <el-tree :data="rightslist" :props="treeProps" show-checkbox
    node-key="id" default-expand-all :default-checked-keys="defKeys" ref="treeRef"></el-tree>

    <span slot="footer" class="dialog-footer">
      <el-button @click="SetRightDialogVisible = false">取 消</el-button>
      <el-button type="primary" @click="allotRights" >确 定</el-button>
    </span>
  </el-dialog>
</div>
</template>

<script>
// 32  roles组件基本布局及获取数据
// 32.1  基础布局    面包屑导航+card(添加角色按钮+table列表)
// 32.1.1 面包屑导航:粘贴rights组件
// 32.1.2 card
// 添加角色按钮   放在一行,使用el-row控制,在用el-col放在一列
// table列表数据获取    为了渲染角色列表,需要拿到对应角色列表数据,调用接口,请求路径：roles,请求方法：get
// 1.分析:(第一层为角色信息,第二层开始为权限说明，权限一共有 3 层权限,最后一层权限，不包含 `children` 属性)
// data是数组,数组中的每一个对象都是一个角色,角色有一个children(当前角色的所有权限,权限分为三级,每一级通过children嵌套,
// 如商品管理/商品列表/添加商品分别为主管的一级\二级\三级权限)
// 2.发起请求: 在data中定义roleList空数组,这样之后后端获取的"角色列表"数据可以直接保存在data中
// 在created中发起首屏数据请求获取所有角色列表数据,this.getRoleList(),在methods中定义this.getRoleList()件将获取的"权限列表"数据直接保存在data中的roleList
// 33  角色列表table数据渲染
//   共有5列,第一列是展开列,可以展开查看对应的所有权限
// 33.1 渲染基本数据
// 33.2 添加角色\编辑\删除功能和用户列表相同,不在重复
// 33.3 角色下权限数据的渲染(第一列展开行)
// 描述:展开某个角色对应的权限,如主管对应的一级权限就是蓝色tag,二级绿色,三级黄色,同时还可以删除
// 1.首先在展开行中拿到当前角色对应的所有权限(角色对应的children就是当前角色拥有的所有权限,只需要拿到当前行的角色信息,在.children就可以拿到所有属性)
// 作用域插槽拿到当前行数据,{{scope.row}}接收下当前行数据;用<pre>{{scope.row}}</pre>美化下,
// scope.row.chlidren就是当前行下的所有权限,chlidren是数组,可以循环chlidren里边每一个对象,就可以把里边所有的一级权限渲染出来
// 在每一个一级权限中又通过children嵌套所有的二级权限,如果最终要渲染出一级\二级\三级结构的话,需要三层for循环的嵌套,最外层for循环渲染一级权限,第二个for循环渲染二级权限,最后一个for循环渲染三级权限
// 1.1首先渲染角色下一级权限,布局:左侧渲染一级权限,右侧渲染二级和三级权限,用栅格系统布局
// 1.1.1 v-for="(item1,i1) in scope.row.children"每一次for循环都要绑定唯一key值,循环一级权限数组,
// {{item1.authName}}渲染一级权限名称,将{{item1.authName}}放在el-tag中
// 1.1.2 美化一级权限  一级权限之间有间隔:el-tag {margin: 7px;}
// 所有权限(行)之间有边框线间隔且第一个权限上边也有一个边框线:先定义.bdtop和.bdbottom两种类,再为el-row绑定动态类:class="['bdbottom']"(传数组,每一行都有一个底边框,因此将bdbottom直接帮给每一行)
// 给第一行上边加一个底边框(判断条件:如果索引列是0就加上边框,否则就不加)三元函数i1 === 0 ? 'bdtop' : ''
// 加小箭头:icon图标 <i class="el-icon-caret-right"></i>
// 1.2 渲染二级权限   在一级权限中通过for循环渲染二级权限,二级权限应该占右侧剩余19列，每一个二级权限都是一个el-row,
// 在这个el-row中，二级权限占左侧的6列，右侧18列三级权限占用；
// 1.2.1  v-for渲染与一级权限相同,v-for="(item2,i2) in item1.children"
// 1.2.2   美化二级权限   每一行之间加一个边框线(给每一个二级加一个顶部边框线,按需将第一个顶部边框线去掉),再为el-row绑定动态类:class="[i2 === 0 ? '': 'bdtop']"
// 1.3 三级权限渲染  v-for渲染与二级权限相同
// 1.4   美化权限数据   右击检查发现小箭头被挤到下二行,如何解决:给整个页面加一个最小宽度即可,在全局样式中给html/body等加 min-width: 1366px;
// 让商品管理\角色列表等纵向居中   右击选中对应那一行el-row,给他加样式display: flex;align-items: center;(直接在页面的style中加,可以实现居中),在style中单独定义类.vcenter
// 谁有需要用就加vcenter即可(第一个和第二个el-row需要)
// 1.5 删除角色下指定权限
// 1.5.1 为三级权限每一个标签提供一个删除(可移除)小图标closable,点击图标弹出一个确认删除的提示框
// 为三级权限每一个tag标签添加可移除图标closable,接着为tag添加点击事件@close="removeRightById()"接着弹框提示是否删除(messageBox组件——确认消息)
// 用户可能点击确定/取消,用.catch捕获用户的取消行为,并且将结果return出去;$confirm返回值是promise,用await和async简化操作,用confirmResult接收返回值,
// 然会进行结果判断,判断用户是否要删除对应权限,如果confirmResult !== 'confirm'表示用户不想删除,直接return并且提示'取消操作'消息;
// 如果没有return表示用户想要删除(用console.log('确认删除')打印下);
// 发起删除的业务请求  请求路径：roles/:roleId/rights/:rightId(两个参数分别是角色id和权限id,权限id就是要删除的那个权限id,角色id就是删除这个角色下那个指定的权限id)   请求方法：delete
// 怎么拿到角色id哪? 点击三级el-tag会触发@close事件,在调用事件处理函数时可以把对应的角色数据传过去@close="removeRightById(scope.row),直接把scope.row传过去就可以(在template模板里边,有作用域插槽),在scope.row中包含角色id
// 还需要权限id,对应的权限就是item3,因为可以@close="removeRightById(scope.row,item3.id)"一块传过去,在处理函数的形参中接收下removeRightById(role, rightId),把对应参数拼接,
// 将roles/:roleId/rights/:rightId变为`roles/${role.id}/rights/${rightId}`(动态参数用模板字符串),调用完之后服务器返回promise，用await和async简化promise操作,从await返回的数据中结构出data重命名为res,判断状态码是否等于200,
// 删除成功后需要重新刷新下页面，this.getRoleList();但是有一个问题是每当确定删除后，权限列表就合上了，需要重新打开才可以看到有无删除，分析：因为删除之后this.getRoleList()是重新获取整个数据列表，table表格重新渲染一次，因此打开的状态就立即合上了；
// 为了防止再关上，只要删除接口调用成功，服务器返回的res中包含最新的数据权限（即原有8个权限，删除1个，这次delete返回值包含最新的7个）即将服务器返回值data重新为角色赋值即可
// 1.5.2 为1级和2级权限添加删除功能
// 33.4  分配权限功能   点击“分配权限”按钮,弹出分配权限的对话框,弹出对话框同时把对应所有权限数据都获取，
//  33.4.1 为分配权限添加点击事件 @click="showSetRightDialog" ,粘贴dialog部分代码并修改，设置:visible.sync为SetRightDialogVisible和width为50%，
// 取消和确认按钮的@click设置SetRightDialogVisible为false,data中声明SetRightDialogVisible为false,点击“分配权限”按钮，调用showSetRightDialog函数，在这个函数中将SetRightDialogVisible设置为true
//  33.4.2  接下来在弹出对话框同时，立即把所有权限数据以树形结构获取到并保存在data中供页面使用，因此在对话框弹出之前获取到所有权限数据;请求后端获取所有权限，我们需要树形数据，因此type:tree;
// 接下来判断请求有没有成功，status==200,失败直接return结束提示错误消息；成功的话将数据保存在data中供页面使用，在data中定义一个数据rightslist空数组，在数据获取成功后给它赋值，
// 33.4.3  接下来将获取到的所有权限数据以树形结构加载到对话框中，引入“tree”组件，:data="rightslist" :props="defaultProps"指定数据绑定的字段(children：实现父子嵌套所用属性；label:看到的文本，我们用的是authName)
// 在data中定义自己的props为treeProps,
//  33.4.4  添加复选框（tree树形控件---可选择） show-checkbox属性；如任意选中一项，我们期望默认选中这一项的id，而不是选中值，
// 怎么样选中id???  node-key(每个树节点的唯一标识属性):为tree加node-key="id"，实现只要勾选节点，选中的就是这个节点对应的id值，即
// 33.4.5 设置点击“分配权限”，弹窗弹出时，tree节点默认展开：default-expand-all：设置所有节点是否展开？
// 33.4.6 实现弹窗打开时角色已有权限默认勾选：default-checked-keys：默认勾选的节点的key数组（即默认要勾选哪些节点，可以提供一个数组，绑定过来即可）
// 为tree绑定:default-checked-keys="defKeys",在data中声明defKeys为空数组，点击“分配权限”按钮同时立即将当前角色上已有的三级权限id添加到defKeys数组中，接下来再展开tree，就可以将已有的权限都加载出来
// 在页面中定义一个递归函数，把角色信息传到递归函数中，通过递归形式把所有三级节点id都保存在一个数组中，再将数组赋值给defKeys，就可实现点击“分配权限”按钮将已有权限都加载出来
// 递归函数getLeafKeys：1.如果当前node节点不包含children，则为三级节点
// 2.不是三级节点，则需要调用递归获取到三级节点：循环当前node所有数组，每循环一项拿到一个子节点item，根据item在调用递归函数，将当前item作为一个节点传过去，arr也传过去，递归完毕之后三级节点id都保存在arr数组中，
// 3.接下来调用函数：我们要在点击“分配权限”按钮时，要在弹出对话框之前把所有三级节点id都获取到，因此要在showSetRightDialog中调用getLeafKeys，在弹窗弹出之前调用this.getLeafKeys()，
// 4. node节点传role,因为role是父级节点，有children属性，可通过递归形式找到三级节点；又因为当前函数拿不到role，因此需要在showSetRightDialog中传过来 @click="showSetRightDialog(scope.row)",
// 再传递到this.getLeafKeys(role)中，再提供一个数组this.defKeys,递归函数调用成功后，数组中保存的是已经拥有的三级权限id,之后再打开弹窗展示树形结构
// 关闭弹窗清空数组（不然测试角色会有主管权限）：添加监听对话框的关闭事件@close="SetRightDialogClosed"
// 33.5 具体分配权限功能（勾选权限，点击确定，向服务器发起请求，将勾选项保存在后端）:
// 33.5.1  请求路径：roles/:roleId/rights  请求方法：post  请求参数：通过 `请求体` 发送给后端
// 解释请求体：如要为测试角色勾选权限，勾选了几个三级权限，此时我们不仅要把所有已选中节点id(三级)和所有半选中id(二级和一级节点),都形成一个字符串，中间以','分割，传送到后端进行权限分配，
// 怎么获取全选和半选id?  getCheckedKeys:返回目前已选中节点id组成的数组； getHalfCheckedKeys:返回目前半选中节点id组成数组；
// 为‘确定’按钮绑定单击事件，@click="allotRights"，为tree加一个引用treeRef,分别用this.$refs.treeRef.getCheckedKeys()和this.$refs.treeRef.getHalfCheckedKeys()获取到所有选中id,用展开运算符将获取到的两个数组合并为一个新数组keys，
// console打印一下keys,没问题之后将数组keys转换为以","隔开的字符串，之后调用接口请求数据，rids：idStr；
// 但如何获取当前id？在点击”分配权限“按钮弹出对话框时可以获取这一行数据(showSetRightDialog(role))，data中声明roleId, this.roleId = role.id即可，
// status==200,弹出消息提示”更新成功“，刷新列表，关闭弹窗
// 转：用户列表的分配角色
export default {
  data() {
    return {
      roleList: [],
      // 控制分配权限对话框的显示与隐藏
      SetRightDialogVisible: false,
      // 所有权限数据
      rightslist: [],
      // 树形控件的属性绑定对象
      treeProps: {
        children: 'children',
        label: 'authName'
      },
      // 默认选中节点的id值的数组
      defKeys: [],
      roleId: '' // 当前即将分配权限的id

    }
  },
  created() {
    this.getRoleList()
  },
  methods: {
    async getRoleList() {
      const { data: res } = await this.$http.get('roles')
      console.log(res)
      if (res.meta.status !== 200) {
        return this.$message.error('获取用户列表失败')
      }
      this.roleList = res.data
    },
    // 根据id删除对应的权限
    async removeRightById(role, rightId) {
      // 弹框提示用户是否删除
      // $confirm返回值是promise,用async和await简化操作
      const confirmResult = await this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).catch(error => error)
      // 如果用户确认删除，返回字符串'confirm'
      //  如果用户取消删除，返回字符串'cancel'
      if (confirmResult !== 'confirm') { return this.$message.info('取消操作') }
      // console.log('确认删除')
      // 请求路径：roles/:roleId/rights/:rightId(地址中包含两个参数，分别是角色id和权限id,即要删除这个角色id下指定的权限id)
      // 用反引号表示字符串,参数:roleId为角色id,
      const { data: res } = await this.$http.delete(`roles/${role.id}/rights/${rightId}`)
      if (res.meta.status !== 200) { this.$message.error('删除权限失败') }
      // this.getRoleList()
      role.children = res.data
    },
    // 展示分配权限对话框
    async showSetRightDialog(role) {
      this.roleId = role.id
      // 请求路径：rights/:type  请求方法：get  请求参数 type  值 list 或 tree , list 列表显示权限, tree 树状显示权限
      // list以列表展示  tree是把所有权限以嵌套关系形成一棵权限树，   我们要把所有权限渲染出一颗树形结构，因此type:tree
      const { data: res } = await this.$http.get('rights/tree')
      if (res.meta.status !== 200) {
        return this.$message.error('获取权限失败')
      }
      // 把获取到的权限数据保存在data中
      this.rightslist = res.data
      // console.log(this.rightslist)获取所有权限数据，一级权限中通过children嵌套二级权限，二级权限中通过children嵌套三级权限
      // 递归获取三级节点id
      // node节点传role,因为role是父级节点，有children属性，可通过递归形式找到三级节点；又因为当前函数拿不到role，因此需要在showSetRightDialog中传过来 @click="showSetRightDialog(scope.row)",
      // 再传递到this.getLeafKeys(role)中，再提供一个数组this.defKeys,递归函数调用成功后，数组中保存的是已经拥有的三级权限id,之后再打开弹窗展示树形结构
      this.getLeafKeys(role, this.defKeys)

      this.SetRightDialogVisible = true
    },
    // 通过递归形式获取角色下所有三级权限id，并保存在数组defKeys中
    // 需要一个节点判断是否是三级节点，还要一个数组进行保存id;传递形参node,arr；node
    getLeafKeys(node, arr) {
      // 如果当前node节点不包含children，则为三级节点
      // 不是三级节点，则需要调用递归获取到三级节点：循环当前node所有数组，每循环一项拿到一个子节点item，根据item在调用递归函数，将当前item作为一个节点传过去，arr也传过去，递归完毕之后三级节点id都保存在arr数组中，
      // 接下来调用函数：我们要在点击“分配权限”按钮时，要在弹出对话框之前把所有三级节点id都获取到，因此要在showSetRightDialog中调用getLeafKeys，在弹窗弹出之前调用this.getLeafKeys()，
      if (!node.children) {
        return arr.push(node.id)
      }
      node.children.forEach(item => // 不是三级节点
        this.getLeafKeys(item, arr))
    },
    // 监听“分配权限”对话框关闭事件
    SetRightDialogClosed() {
      this.defKeys = []
    },
    // 点击为角色分配权限
    async allotRights() {
      const keys = [
        // 所有已选中id数组，...为展开运算符（如数组id=[1,2,3],...为直接将1,2,3拿出来放在keys中）
        ...this.$refs.treeRef.getCheckedKeys(),
        // 所有半选中id数组，通过展开运算符（...）将两个数组合并成一个新数组keys
        ...this.$refs.treeRef.getHalfCheckedKeys()
      ]
      // console.log(keys, 'keys')//可以为测试角色2添加权限检测，点击商品管理下的两个全选，共获取4个id
      // 转换成以","隔开的字符串
      const idStr = keys.join(',')
      const { data: res } = await this.$http.post(`roles/${this.roleId}/rights`, { rids: idStr })
      if (res.meta.status !== 200) {
        this.$message.error('更新权限失败')
      }
      this.$message.success('更新权限成功')
      this.getRoleList()
      this.SetRightDialogVisible = false
    }
    // !!!  element中 event就使用@写在标签中  methods就要定义refs，使用this.$refs....
    // 请求路径：roles/:roleId/rights  请求方法：post  请求参数：通过 `请求体` 发送给后端

  }

}
</script>

<style lang="less" scoped>
.el-tag {
  margin: 7px;
}
.bdtop {
  border-top:1px solid #eee
}
.bdbottom {
border-bottom:1px solid #eee
}
.vcenter {
  display: flex;
  align-items: center;
}

</style>
